介紹 App Router 其他比較奇巧型的路由(?)定義。
除了定義 page 作為路由的主要頁面外,也可以定義額外的頁面區塊,跟主要頁面平行載入,並且這些額外區塊也有各自的 loading , error 等狀態。
console
├── @menu
│ └── page.tsx
├── layout.tsx
└── page.tsx
加上 @
的目錄就是額外定義的頁面區塊。
可以在 layout 中去定義額外區塊的位置。
export interface LayoutProps {
children: React.ReactNode;
menu: React.ReactNode;
}
// ...
export default function Layout({ children, menu }: LayoutProps) {
return (
<div className="grid grid-cols-12">
<div className="col-span-2">{menu}</div>
<div className="col-span-10">{children}</div>
</div>
);
}
類似這樣,定義一個固定在左側的選單區塊。
除了做出獨立區塊外,也能用在因檢查而需要替換整個路由的情況,例如檢查是否登入,沒登入的話只能看到公開區塊的內容。
app
├── @public
│ └── default.tsx
├── layout.tsx
└── page.tsx
// layout.tsx
// ...
export default async function RootLayout({
children,
public: publicRoute,
}: {
children: React.ReactNode;
public: React.ReactNode;
}) {
const isAuthenticated = false;
return (
<html lang="en">
<body id="__next">
<StyledEngineProvider injectFirst>
<CssBaseline /> {/* 加上 CssBaseline */}
<ThemeProvider theme={primaryTheme}>
{/* 根據是否登入決定要顯示哪個路由 */}
{isAuthenticated ? children : publicRoute}
</ThemeProvider>
</StyledEngineProvider>
</body>
</html>
);
}
說是路由其實更類似 Vue 的 named slot 功能,只是藉由路由解析將一些原本需要全塞在 page 中的邏輯改成用目錄呈現,讓程式碼更獨立且乾淨。
截擊路由,當某頁面想好好地導向某個路由時,從中攔截並替換成另外定義的路由。
可以跟 Parallel Router 並用,達成不切換頁面快顯目標路由內容的功能。
以點擊清單後彈出畫面顯示概要內容為例。
user
├── @modal
│ ├── (..)user
│ │ └── [id]
│ │ └── page.tsx
│ └── default.tsx
├── [id]
│ └── page.tsx
├── layout.tsx
└── page.tsx
先看到 @modal/(..)user/[id]
這段目錄結構,其中 (..)
就是 Intercepting Routes 主要的定義方式,有幾個種類:
(.) 參照從同一層路由開始
(..) 參照從上一層路由開始
(..)(..) 參照從上上層路由開始
(...) 參照從根路由開始,也就是從 app 那層開始
之後當 user/page
中嘗試要導向 user/[id]
的路由時,就會發現這邊有截斷路由的定義,並改用這邊的路由做顯示。
而因為 @modal
是 Parallel Route,就會變成沒切換頁面而是替換 modal 內容的效果,變成彈出視窗顯示。
// user/layout.tsx
export interface LayoutProps {
children: React.ReactNode;
modal: React.ReactNode;
}
// ...
export default function Layout({ children, modal }: LayoutProps) {
return (
<div>
<h1>User Layout</h1>
{children}
{modal}
</div>
);
}
// user/@modal/(..)user/[id]/page.tsx
'user client';
import { Card, Modal } from '@mui/material';
import { useRouter } from 'next/navigation';
export interface IdProps {
params: {
id: string;
};
}
export default function User({ params: { id } }: IdProps) {
const router = useRouter();
return (
<Modal open>
<Card>
<h1>Welcome to User! {id}</h1>
<button onClick={() => router.back()}>Back</button>
</Card>
</Modal>
);
}
關閉 Modal 的方法不用 state ,而是用 router.back()
退回上個路由就可以。
這樣的結構有幾個好處: